package com.wnxy.sddfp.system.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wnxy.sddfp.client.bank.api.TradeLogApi;
import com.wnxy.sddfp.common.model.dto.bank.BankCardAddDTO;
import com.wnxy.sddfp.common.model.dto.bank.CardBindDto;
import com.wnxy.sddfp.common.model.dto.bank.SignatureDto;
import com.wnxy.sddfp.common.model.dto.user.LoginDto;
import com.wnxy.sddfp.common.model.dto.user.UserRegisterDto;
import com.wnxy.sddfp.common.model.exception.Assert;
import com.wnxy.sddfp.common.model.result.enums.impl.BusinessCode;
import com.wnxy.sddfp.common.model.result.enums.impl.ResultCode;
import com.wnxy.sddfp.common.model.utils.JwtTemplate;
import com.wnxy.sddfp.common.model.vo.LoginVo;
import com.wnxy.sddfp.common.model.vo.UserInfoVo;
import com.wnxy.sddfp.system.entity.User;
import com.wnxy.sddfp.system.entity.UserCard;
import com.wnxy.sddfp.system.entity.UserDetail;
import com.wnxy.sddfp.system.mapper.UserCardMapper;
import com.wnxy.sddfp.system.mapper.UserDetailMapper;
import com.wnxy.sddfp.system.mapper.UserMapper;
import com.wnxy.sddfp.system.service.IUserDetailService;
import com.wnxy.sddfp.system.service.IUserService;
import com.wnxy.sddfp.system.utils.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author yellow docker
 * @since 2024-07-29
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    private final UserDetailMapper userDetailMapper;
    private final PasswordEncoder passwordEncoder;// 密码加密器
    private final StringRedisTemplate stringRedisTemplate;
    private final IUserDetailService userDetailService;
    private final JwtTemplate jwtTemplate;
    private final UserCardMapper userCardMapper;
    private final UserMapper userMapper;
    @Autowired
    private TradeLogApi tradeLogApi;

    public UserServiceImpl(UserDetailMapper userDetailMapper, PasswordEncoder passwordEncoder, StringRedisTemplate stringRedisTemplate, IUserDetailService userDetailService, JwtTemplate jwtTemplate, UserCardMapper userCardMapper, UserMapper userMapper) {
        this.userDetailMapper = userDetailMapper;
        this.passwordEncoder = passwordEncoder;
        this.stringRedisTemplate = stringRedisTemplate;
        this.userDetailService = userDetailService;
        this.jwtTemplate = jwtTemplate;
        this.userCardMapper = userCardMapper;
        this.userMapper = userMapper;
    }


    @Override
    public UserInfoVo findById(Long userId) {
        // 返回字段分析：user表（userName,identityCode）
        // 返回字段分析：user_detail表（userCode）

        // 根据id查询数据库
        User user = this.getById(userId);
        // 自定义断言判断，减少if判断，让代码更简洁
        Assert.error(user == null, ResultCode.DATA_IS_WRONG);
        // 返回结果
        UserInfoVo userInfoVo = new UserInfoVo();
        userInfoVo.setUserName(user.getUserName());
        userInfoVo.setIdentityCode(user.getIdentityCode());

        // 根据用户id查询用户编码； 用户与用户详情共用主键
        UserDetail userDetail = userDetailService.getById(userId);
        userInfoVo.setUserCode(userDetail.getUserCode());

        return userInfoVo;
    }


    /**
     * 注册新用户。
     *
     * @param userRegistDto 用户注册数据传输对象，包含用户注册所需信息。
     * @Transactional 注解确保该方法在一个数据库事务中执行，如果发生异常则回滚。
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void register(UserRegisterDto userRegistDto) {
        /**
         * 业务逻辑:
         *   1、通过手机号码、用户名名判断用户是否存在
         *   2、保存用户 （用户名 + 密码 + 手机号）
         *
         *  操作表:
         *    1、添加用户
         *         操作2个表
         */

        // 根据手机号查询是否存在已注册用户。
        // 校验手机号码是否存在
        User phoneUser = this.getOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getPhone, userRegistDto.getPhone()));
        // 如果手机号已存在，则抛出异常。
        Assert.error(phoneUser != null, BusinessCode.PHONE_EXISTED); // 如果phoneUser不为null，则抛出异常

        // 根据用户名查询是否存在已注册用户。
        // 校验用户名是否存在
        User nameUser = this.getOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getUserName, userRegistDto.getUserName()));
        // 如果用户名已存在，则抛出异常。
        Assert.error(nameUser != null, BusinessCode.USER_EXISTED); // 如果nameUser不为null，则抛出异常

        // 创建新用户对象，并设置相关信息。
        // 保存用户
        User user = new User();
        user.setPhone(userRegistDto.getPhone());
        user.setUserName(userRegistDto.getUserName());
        // 对密码进行加密处理。
        user.setPwd(passwordEncoder.encode(userRegistDto.getPwd()));
        user.setUserType("1");
        user.setUserStatus(false);
        // 保存用户到数据库。
        this.save(user);

        // 创建用户详情对象，并设置相关信息。
        // 保存用户详情
        UserDetail userDetail = new UserDetail();
        userDetail.setId(user.getId());
        userDetail.setUserName(user.getUserName());
        userDetail.setPhone(user.getUserName());
        userDetail.setBorrowMoney(new BigDecimal("0"));
        // 生成用户代码。
        userDetail.setUserCode("UC_" + UUID.randomUUID().toString().substring(0, 10));
        userDetail.setCardBindStatus(false);
        // 插入用户详情到数据库。
        userDetailMapper.insert(userDetail);
    }


    @Override
    public LoginVo login(LoginDto loginDto) {
        // 判断是否是手机号登陆
        LoginVo loginVo = loginDto.getIsPhoneLogin() ? userPhoneLogin(loginDto) : userPwdLogin(loginDto);

        Assert.error(Objects.isNull(loginVo), BusinessCode.USER_LOGIN_ERROR);

        return loginVo;
    }

    /**
     * 用户绑卡：
     * 1、从token中获取userId
     * 2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
     * 3、如果未绑卡，保存保卡信息到u_user_card表
     * 4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中
     * 5、生成请求流水号，用来访问托管代理，并更新u_user_detail表中流水号字段REQ_CODE
     * 6、远程访问托管代理服务（暂不实现）
     * 业务逻辑:
     * u_user_detail 表    CARD_BIND_STATUS 判断是否绑卡没有
     * u_user_card   表    存储绑卡信息
     * u_user_card 表中以存储绑卡信息
     * u_user        表中的字段进行更新 realName
     * u_user_detail 表中的字段进行更新 REQ_CODE
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public SignatureDto addBankAccount(CardBindDto cardBindDto) {
        String userId = TokenUtils.getUserId();
        System.out.println("userId = " + userId);

        // 2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
        UserDetail userDetail = userDetailMapper.selectById(userId);
        Assert.error(userDetail.getCardBindStatus(), BusinessCode.USER_CARD_ALREADY_BINDED);

        // 3、如果未绑卡，保存保卡信息到u_user_card表
        // 3.1 先判断是否已经绑卡
        Long ucCount = userCardMapper.selectCount(Wrappers.lambdaQuery(UserCard.class).eq(UserCard::getCardNo, cardBindDto.getCardNo()));
        Assert.error(ucCount > 0, BusinessCode.USER_CARD_ALREADY_BINDED);

        // 3.2 未绑卡再插入数据
        UserCard userCard = new UserCard();
        userCard.setUserId(Long.valueOf(userId));
        userCard.setCardNo(cardBindDto.getCardNo());
        userCard.setPhone(userDetail.getPhone());
        userCard.setStatus(false);
        userCardMapper.insert(userCard);

        // 4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中
        userMapper.update(null, Wrappers.lambdaUpdate(User.class)
                .set(User::getRealName, cardBindDto.getRealName())
                .set(User::getIdentityCode, cardBindDto.getIdentityCode())
                .eq(User::getId, userId)
        );

        // 5、生成请求流水号，用来访问托管代理，并更新u_user_detail表中流水号字段REQ_CODE
        // String reqCode = UniqueIdGenerator.generator(PrefixEnums.REQ.getPrefix());
        String reqCode = UUID.randomUUID().toString().replace("-", "").substring(0, 10);
        userDetailMapper.update(null, Wrappers.lambdaUpdate(UserDetail.class)
                .set(UserDetail::getReqCode, reqCode)
                .eq(UserDetail::getId, userId)
        );
        // 6、远程访问托管代理服务
        // 通过Openfeign实现远程调用 (像调用本地方法一样，进行远程调用)

        BankCardAddDTO bankCardAddDTO = new BankCardAddDTO();
        bankCardAddDTO.setReqCode(reqCode);
        bankCardAddDTO.setUserId(Long.valueOf(userId));

        SignatureDto signatureDto = tradeLogApi.proxyBindCardLog(bankCardAddDTO);
        return signatureDto;
    }


    private LoginVo userPwdLogin(LoginDto loginDto) {
        // 通过 用户名查询 User
        User user =
                getOne(Wrappers.lambdaQuery(User.class)
                        .eq(User::getUserName, loginDto.getLoginName()));

        // 判断 一下
        Assert.error(Objects.isNull(user), BusinessCode.USER_LOGIN_ERROR);

        // 拿到密码
        String dbPwd = user.getPwd();

        // 校验密码 (自己是文明的密码、数据库是加密的密码)
        // matches()  会自动进行判断
        boolean isSuccess = passwordEncoder.matches(loginDto.getLoginSecret(), dbPwd);

        if (isSuccess) {
            return getLoginVo(user);
        }
        return null;
    }

    // 把 user对象转成 loginVo 返回给前端
    private LoginVo getLoginVo(User user) {
        Map<String, Object> payload = new HashMap<>();
        payload.put("userId", user.getId());
        payload.put("phone", user.getPhone());

        // 把载荷 通过工具生成 token
        String token = jwtTemplate.createToken(payload);

        // 存储到Redis
        stringRedisTemplate.opsForValue()
                .set("userToken" + user.getId(),
                        token,
                        Duration.ofMinutes(30));

        return LoginVo.builder().userName(user.getUserName())
                .id(user.getId())
                .phone(user.getPhone())
                .token(token).build();
    }


    private LoginVo userPhoneLogin(LoginDto loginDto) {
        /**
         *  1、手机号码 和  短信进行登录
         *    判断用户是否存在   直接登录
         *    判断用户不否存在   自动注册用户 、进行登录
         */
        User user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getPhone, loginDto.getLoginName()));
        // 校验 验证码
        // 拿到 用户的 验证码 和redis 中的验证码进行比较
        String loginCode = loginDto.getLoginSecret();
        // 这个key 和 存储到redis中的key需要保持一致
        String keyCode = "SMS:" + loginDto.getLoginName();
        String redisCode = stringRedisTemplate.opsForValue().get(keyCode);
        // 判断
        Assert.error(StringUtils.isEmpty(redisCode), BusinessCode.USER_VERIFY_CODE_ERROR);

        if (loginCode.equals(redisCode)) {
            // 登录成功
            if (Objects.isNull(user)) {
                // 校验 验证码
                // 存储用户 自动注册
                UserRegisterDto userRegisterDto = new UserRegisterDto();
                // 用户名可以随机 或者使用电话号码
                userRegisterDto.setUserName(loginDto.getLoginName());
                // 默认密码 给 123456
                userRegisterDto.setPwd(passwordEncoder.encode("123456"));
                userRegisterDto.setPhone(loginDto.getLoginName());
                saveUser(userRegisterDto);
            }
            // 校验 验证码
            // 保存完毕后 要返回user
            user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getPhone, loginDto.getLoginName()));
            return getLoginVo(user);
        }
        return null;
    }

    private void saveUser(UserRegisterDto userRegisterDto) {
        User u_user = new User();
        // 设置手机号码
        u_user.setPhone(userRegisterDto.getPhone());
        // 设置用户名
        u_user.setUserName(userRegisterDto.getUserName());
        // 密码  设置加密 存储到mysql中
        u_user.setPwd(passwordEncoder.encode(userRegisterDto.getPwd()));
        // 用户hutool 加密    后面的登录的方法 解密一定要 保持一致 不能随意
//        u_user.setPwd(BCrypt.hashpw(userRegisterDto.getPwd(), BCrypt.gensalt()));
        // 设置 用户类型  个人用户1  企业用户2
        u_user.setUserType("1");
        // 状态 默认0
        u_user.setUserStatus(false);

        save(u_user);

        UserDetail u_user_detail = new UserDetail();

        u_user_detail.setId(u_user.getId());
        u_user_detail.setUserName(u_user.getUserName());
        u_user_detail.setPhone(u_user.getPhone());
        // 借款金额 0
        u_user_detail.setBorrowMoney(new BigDecimal("0"));
        // 用户code  随机的字符串
        u_user_detail.setUserCode("UC_" + UUID.randomUUID().toString().substring(0, 10));
        // 绑卡状态 0
        u_user_detail.setCardBindStatus(false);

        userDetailService.save(u_user_detail);
    }


}
